////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer.Interfaces
{
    using System;
    using Microsoft.SPOT;
    using Microsoft.SPOT.Hardware;
    using Gadgeteer.Modules;

    internal class NativePwmOutput : Socket.SocketInterfaces.PwmOutput
    {
        private Microsoft.SPOT.Hardware.PWM _port;
        private Cpu.PWMChannel _channel;
        private Socket _socket;

        private bool _invert;
        private bool _started;

        public NativePwmOutput(Socket socket, Socket.Pin pin, bool invert, Module module, Cpu.PWMChannel channel)
        {
            if (channel == Cpu.PWMChannel.PWM_NONE)
            {
                Socket.InvalidSocketException.ThrowIfOutOfRange(pin, Socket.Pin.Seven, Socket.Pin.Nine, "PWM", module);

                // this is a mainboard error that should not happen (we already check for it in SocketInterfaces.RegisterSocket) but just in case...
                throw Socket.InvalidSocketException.FunctionalityException(socket, "PWM");
            }

            _channel = channel;
            _socket = socket;
            _invert = invert;
        }

        public override bool IsActive
        {
            get
            {
                return _port != null;
            }
            set
            {
                if ((_port != null) == value)
                    return;

                if (value)
                {
                    _port = new PWM(_channel, 1, 0.5, _invert);
                    _started = false;
                }
                else
                {
                    if (_started)
                        _port.Stop();

                    _port.Dispose();
                    _port = null;
                }
            }
        }

        public override void Set(double frequency, double dutyCycle)
        {
            if (frequency < 0)
                throw new ArgumentException("frequency");

            if (dutyCycle < 0 || dutyCycle > 1)
                throw new ArgumentException("dutyCycle");

            if (_port == null)
            {
                _port = new PWM(_channel, frequency, dutyCycle, _invert);

                _port.Start();
                _started = true;
            }
            else
            {
                if (_started)
                    _port.Stop();

                _port.Frequency = frequency;
                _port.DutyCycle = dutyCycle;

                _port.Start();
                _started = true;
            }
        }

        public override void Set(uint period, uint highTime, Socket.SocketInterfaces.PwmScaleFactor factor)
        {
            if (_port == null)
            {
                _port = new PWM(_channel, period, highTime, (PWM.ScaleFactor)factor, _invert);

                _port.Start();
                _started = true;
            }
            else
            {
                if (_started)
                    _port.Stop();

                _port.Scale = (PWM.ScaleFactor)factor;
                _port.Period = period;
                _port.Duration = highTime;

                _port.Start();
                _started = true;
            }
        }

        public override void Dispose()
        {
            _port.Dispose();
        }
    }


    /// <summary>
    /// Represents pulse width modulation (PWM) output using a single socket pin.
    /// </summary>
    public class PWMOutput
    {
        internal readonly Socket.SocketInterfaces.PwmOutput Interface;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary>
        /// </summary>
        /// <remarks>This automatically checks that the socket supports Type P, and reserves the pin.
        /// An exception will be thrown if there is a problem with these checks.</remarks>
        /// <param name="socket">The socket that supports pulse width modulation (PWM) output.</param>
        /// <param name="pin">The pin on the socket that supports PWM.</param>
        /// <param name="invert">Whether to invert the output voltage.</param>
        /// <param name="module">The module using this PWM output interface, which can be null if unspecified.</param>
        public PWMOutput(Socket socket, Socket.Pin pin, bool invert, Module module)
        {
            socket.EnsureTypeIsSupported('P', module);
            socket.ReservePin(pin, module);

            Cpu.PWMChannel channel = Cpu.PWMChannel.PWM_NONE;
            switch (pin)
            {
                case Socket.Pin.Seven:
                    channel = socket.PWM7;
                    break;

                case Socket.Pin.Eight:
                    channel = socket.PWM8;
                    break;

                case Socket.Pin.Nine:
                    channel = socket.PWM9;
                    break;
            }

            if (channel == Cpu.PWMChannel.PWM_NONE && socket.PwmOutputIndirector != null)
                Interface = socket.PwmOutputIndirector(socket, pin, invert, module);

            else
                Interface = new NativePwmOutput(socket, pin, invert, module, channel);
        }

        /// <summary>
        /// Sets the frequency and duty cycle of the <see cref="PWMOutput"/> interface and starts the PWM signal.
        /// </summary>
        /// <param name="frequency">Required frequency in Hertz.</param>
        /// <param name="dutyCycle">Duty cycle from 0-1.</param>
        public void Set(int frequency, double dutyCycle)
        {
            Interface.Set(frequency, dutyCycle);
        }

        /// <summary>
        /// Sets the period and high time of the <see cref="PWMOutput"/> interface and starts the PWM signal.
        /// </summary>
        /// <param name="period_ns">Period of the signal in nanoseconds.</param>
        /// <param name="highTime_ns">High time of the signal in nanoseconds.</param>
        public void SetPulse(uint period_ns, uint highTime_ns)
        {
            Interface.Set(period_ns, highTime_ns, Socket.SocketInterfaces.PwmScaleFactor.Nanoseconds);
        }

        /// <summary>
        /// Gets or sets a Boolean value that indicates whether the PWM interface is active, <b>true</b> if active
        /// otherwise <b>false</b>.
        /// </summary>
        public bool Active
        {
            get { return Interface.IsActive; }
            set { Interface.IsActive = value; }
        }

        /// <summary>
        /// Returns the <see cref="Socket.SocketInterfaces.PwmOutput" /> for a <see cref="PWMOutput" /> interface.
        /// </summary>
        /// <param name="this">An instance of <see cref="PWMOutput" />.</param>
        /// <returns>The <see cref="Socket.SocketInterfaces.PwmOutput" /> for <paramref name="this"/>.</returns>
        public static explicit operator Socket.SocketInterfaces.PwmOutput(PWMOutput @this)
        {
            return @this.Interface;
        }

        /// <summary>
        /// Releases all resources used by the interface.
        /// </summary>
        public void Dispose()
        {
            Interface.Dispose();
        }
    }
}
